Modul:Vorlage:Normdaten
Zur Navigation springen
Zur Suche springen
Die Dokumentation für dieses Modul kann unter Modul:Vorlage:Normdaten/Doku erstellt werden
--[=[ 2014-07-07
{{Normdaten}}
]=]
local template = {
errorStyle = "",
hooks = { },
msg = {
formDate = "Datumsformat: JJJJ-MM-TT",
formYes = "Gültig wäre 'Ja'",
Invalid = "Parameterwert für '%s' ungültig: %s",
DNBbad = "DNB-Code ungültig",
DNBpre = "DNB-Format vor 2012 ungeeignet",
GKD_SWD = " passt nicht zu Normdaten-Typ",
LCCNpre = "LCCN-Präfix '%s' ungeeignet",
LCCNser = "LCCN-serial '%s' ungeeignet",
LCCNser0 = "LCCN-serial fehlt",
LCCNyear = "LCCN-Jahr '%s' ungeeignet",
TYPnull = [==[Bitte den Parameter TYP ausfüllen
([[Hilfe:GND#Entitätenliste|Hilfe]]).
Als Werte sind p, k, v, w, s oder g
in Kleinschreibung möglich.
Personen gehören zum Typ p.
Für nicht individualisierte Namensdatensätze
bitte p (Person) verwenden
und zusätzlich GNDfehlt = ja eintragen.]==],
TYPval = [==[Gültig sind nur p, k, v, w, s oder g
und nur Kleinschreibung
([[Hilfe:GND#Entitätenliste|Hilfe]]).
Für Namensdatensätze bitte p (Person) verwenden
und zusätzlich GNDfehlt=ja eintragen.]==],
Unknown = "Parametername unbekannt:",
Upcase = "Großschreibung beim Parameternamen erforderlich:"
}, -- msg
params = {
TYP = { hooks = "checkTYP" },
GND = { hooks = "checkGND" },
LCCN = { hooks = "checkLCCN" },
VIAF = { hooks = "checkNumeric" },
NDL = { hooks = "checkNumeric" },
GNDName = { hooks = "checkGND2012" },
GNDfehlt = { hooks = "checkJa1" },
GNDCheck = { hooks = "checkDate" },
REMARK = { lapsus = false },
PND = { hooks = "checkGND2012" },
GKD = { hooks = "check_GKD_SWD" },
SWD = { hooks = "check_GKD_SWD" },
SELIBR = { hooks = "checkNumeric" }
}, -- params
sub = "Normdaten/sub"
}; -- template
local factory = function ( attempt, allowX )
-- Retrieve plain digits of attempt
-- Precondition:
-- attempt -- string; with digits (+xX) and hyphens, not trimmed
-- allowX -- number; of (last) position for permitted xX
-- boolean; xX at last position permitted
-- Postcondition:
-- Returns table; success
-- [1]...[8]/[10]...[13] -- digits 0...9
-- 10 at last position
-- .hyphens -- number of hyphens
-- .type -- number of digits
-- number; no string or bad length or data
-- 0 -- no string
-- >0 -- unexpected char at position (trimmed)
local r;
if type( attempt ) == "string" then
local c, i;
local j = 0;
local k = 1;
local s = mw.text.trim( attempt );
local n = mw.ustring.len( s );
r = { hyphens = 0 };
for i = 1, n do
c = mw.ustring.codepoint( s, i, i + 1 );
if c >= 48 and c <= 57 then
j = j + 1;
r[ j ] = c - 48;
k = false;
elseif c == 45 then -- hyphen
if i > 1 and i < n then
r.hyphens = r.hyphens + 1;
k = i;
else
r = j;
break;
end
elseif c == 88 or c == 120 then -- X x
j = j + 1;
if allowX and i == n then
if allowX == true or allowX == n then
r[ j ] = 10;
else
r = j;
end
else
r = j;
end
break;
else
r = j;
break;
end
end -- for i
if type( r ) == "table" then
r.type = j;
end
else
r = 0;
end
return r;
end -- factory()
local failsafe = function ( adjust )
-- Create pattern to detect nearly every occurrence of a template
-- Precondition:
-- adjust -- string; template title
-- Postcondition:
-- Returns string with pattern
local start = mw.ustring.sub( adjust, 1, 1 );
local scan = mw.ustring.sub( adjust, 2 );
if adjust:match( " " ) then
scan = scan:gsub( " ", "[%s_]" );
end
return "(.*{{)[%s]*([%w]*)(:?)[%s]*(["
.. mw.ustring.upper( start ) .. mw.ustring.lower( start )
.. "]" .. scan .. ")[%s]*[|}]"
end -- failsafe()
local fair = function ( assert )
-- Compute check digit (11 minus modulo 11) for descending factor
-- Precondition:
-- assert -- table; as of factory()
-- .type -- number of digits including check digit
-- Postcondition:
-- Returns checksum
local i;
local n = assert.type;
local k = n;
local r = 0;
for i = 1, n - 1 do
r = r + k * assert[ i ];
k = k - 1;
end -- for i
return ( 11 - r % 11 );
end -- fair()
local fault = function ( append )
-- Add key to collection string and insert separator
-- Precondition:
-- append -- string; to be appended
-- Uses:
-- >< template.say
if template.say then
template.say = mw.ustring.format( "%s\n\n%s",
template.say, append );
else
template.say = append;
end
end -- fault()
local fiat = function ( arglist )
-- Perform parameter analysis
-- Precondition:
-- arglist -- table; template parameters
-- Uses:
-- > template.msg
-- > template.hooks
-- >< template.params
-- >< template.say
-- fault()
local e, k, v;
local up = false;
for k, v in pairs( arglist ) do
e = template.params[ k ];
if e then
e.value = v;
else
if not up then
up = { };
end
up[ k ] = v;
end
end -- for k, v
if up then
local un = false;
for k, v in pairs( up ) do
e = template.params[ mw.ustring.upper( k ) ];
if e and not e.value then
e.value = v;
else
if not un then
un = { };
end
un[ k ] = v;
up[ k ] = nil;
end
end -- for k, v
if next( up ) then
local say = template.msg.Upcase;
local sep = "";
for k in pairs( up ) do
say = mw.ustring.format( "%s%s '%s'", say, sep, k );
sep = ",";
end -- for k
fault( say );
end
if un then
local say = template.msg.Unknown;
local sep = "";
for k in pairs( un ) do
say = mw.ustring.format( "%s%s '%s'", say, sep, k );
sep = ",";
end -- for k
fault( say );
end
end -- up?
for k, v in pairs( template.params ) do
e = template.params[ k ];
if e.value == "" then
e.value = nil;
end
if e.hooks then
template.hooks[ e.hooks ]( k, e );
end
end -- for k, v
end -- fiat()
local find = function ( area, access )
-- Find template transclusion in text
-- Precondition:
-- area -- source text to be scanned
-- access -- template name in standard notation
-- Return:
-- >0 -- point after transclusion begin in page
-- -1 -- unexpected format
-- false -- not found
local r = false;
local scan = failsafe( access );
local s;
local start, space, colon, seek = mw.ustring.match( area, scan );
if seek then
if seek ~= access or not lazy then
if colon then
s = mw.ustring.lower( mw.site.namespaces[10].name );
space = mw.ustring.lower( space );
if space == s or space == "template" then
r = -1;
else
r = false;
end
elseif space == "" then
r = -1;
elseif lazy then
r = false;
elseif mw.ustring.match( r, access ) then
if r:match( "_" ) then
r = -1;
end
else
r = false;
end
end
end
return r;
end -- find()
local finder = function ( access, area )
-- Check single standard transclusion format of template transclusion
-- Precondition:
-- access -- template name in standard notation
-- area -- source text of page
-- Return:
-- >0 -- standard format; point after transclusion begin in page
-- -1 -- unexpected format
-- -2 -- not found -- transclusion in transcluded page
-- -3 -- multiple transclusions
local r;
local lazy = not access:match( " " );
local scan = mw.ustring.sub( access, 2 );
local story = area;
if not lazy then
scan = scan:gsub( " ", "[ _]" );
end
r = mw.ustring.find( story, scan, 1, lazy );
if r then
local k = 1000;
scan = "^" .. failsafe( access );
if r > k then
story = mw.ustring.sub( story, r - k );
end
story = story:gsub( "<!--[^>]*-->", "" );
r = find( story, scan, access );
if r then
if r > 0 then
if find( scan,
mw.ustring.sub( story, r ),
access ) then
r = -3;
end
end
else
r = -2;
end
else
r = -2;
end
return r;
end -- finder()
local flop = function ( about, assign, additional )
-- Complain about invalid template parameter value
-- Precondition:
-- about -- string; parameter name
-- assign -- table; parameter value at assign.value
-- add -- string or nil; additional information
-- Uses:
-- > template.msg
-- fault()
local s = mw.ustring.format( template.msg.Invalid,
about, assign.value );
if additional then
s = s .. " * " .. additional;
end
fault( s );
end -- flop()
local DNBfaith = function ( assert, ancestor )
-- Compute DNB (also GND, ZDB) check digit and verify
-- Precondition:
-- assert -- table; as of factory()
-- .type -- until 11 including check digit
-- ancestor -- true: 2011 mode
-- Postcondition:
-- Returns true: check digit matches
local k = fair( assert ) % 11;
if ancestor then
k = 11 - k;
end
return ( k == assert[ assert.type ] );
end -- DNBfaith()
local DNBvalid = function ( attempt, also )
-- Is this DNB (also GND, ZDB) formally correct (check digit)?
-- Precondition:
-- attempt -- string with any presumable DNB code
-- also -- string or nil; optional requirement DMA GND SWD
-- currently not implemented
-- DMA starting with 3 and no hyphen
-- GND not DNB2011
-- SWD DNB2011 starting with 4 or 7 and no X check
-- Postcondition:
-- Returns number of digits or 2011, if valid
-- false if not correct, bad data or check digit wrong
local s = mw.text.trim( attempt );
local j = s:find( "/", 5, true );
local r = false;
local dnb;
if j then
s = attempt:sub( 1, j - 1 );
end
j = s:find( "-", 2, true );
if j then
if j > 3 and j <= 8 then
if s:match( "^[0-9]+-[0-9xX]$" ) then
dnb = factory( s, #s );
end
end
elseif #s > 7 then
if s:match( "^[0-9]+$" ) then
dnb = factory( s, false );
end
end
if type( dnb ) == "table" then
if j then
if DNBfaith( dnb, true ) then
r = 2011;
end
else
if DNBfaith( dnb, false ) then
r = dnb.type;
end
end
end
return r;
end -- DNBvalid()
local LCCNfactory = function ( attempt, allow )
-- Retrieve segments of LCCN attempt (format since 2001)
-- Precondition:
-- attempt -- string with presumable LCCN
-- allow -- false or string: "/"
-- Postcondition:
-- Returns table; success
-- false if not correct, bad data
-- 2013-08-25
local r = false;
local pat = "^[%s]*([%a]*)(/?)(%d[%S]+)[%s]*$";
local pre, sep, s = attempt:match( pat );
if pre and s then
local year, serial;
if pre == "" then
pre = false;
if sep ~= "" then
s = false;
end
elseif #pre > 3 then
s = false;
else
pre = pre:lower();
end
if s then
if allow ~= "/" or sep == "/" then
if sep == "/" then
year, serial = s:match( "^([%d]+)/([%d].+)$" );
elseif s:find( "-", 2, true ) then
year, serial = s:match( "^([%d]+)%-([%d].+)$" );
else
year = s:match( "^([%d]+)" );
if year then
if #year == 8 then
year = s:sub( 1, 2 );
serial = s:sub( 3 );
elseif #year == 10 then
year = s:sub( 1, 4 );
serial = s:sub( 5 );
else
year = false;
serial = s;
end
elseif tonumber( s ) then
serial = s;
end
end
end
if year then
if #year == 4 then
local n = tonumber( year );
if n <= 2000 then
-- 2000 -> "00"
serial = false;
elseif n > tonumber( os.date( "%Y" ) ) then
serial = false;
end
elseif #year ~= 2 then
serial = false;
end
end
if serial then
r = { pre = pre, serial = serial };
if year then
r.year = year;
end
if serial:find( "/", 2, true ) then
local q;
serial, q = serial:tolower()
:match( "^([%d]+)/([a-z]+)$" );
if q == "dc" or
q == "mads" or
q == "marcxml" or
q == "mods" then
r.serial = serial;
r.qualifier = q;
end
end
serial = serial:match( "^0*([1-9][%d]*)$" );
if not serial then
r = false;
elseif #serial < 6 then
serial = mw.ustring.format( "%06d",
tonumber( serial ) );
elseif #serial > 6 then
r = false;
end
end
end
end
return r;
end -- LCCNfactory()
local LCCNformat = function ( assigned, achieve )
-- Standard or hyphen or slash formatting of LCCN
-- Precondition:
-- assigned -- table; as of LCCNfactory(), and valid
-- achieve -- additional formatting desires, like "-" or "/"
-- Postcondition:
-- Returns string with letters, digits and hyphens
-- 2013-07-14
local r;
if assigned.pre then
r = assigned.pre;
else
r = "";
end
if assigned.year then
if achieve == "/" and r ~= "" then
r = r .. "/";
end
r = r .. assigned.year;
if achieve then
r = r .. achieve;
end
end
if assigned.serial then
r = r .. assigned.serial;
end
if assigned.qualifier then
r = r .. "/" .. assigned.qualifier;
end
return r;
end -- LCCNformat()
local LCCNforward = function ( attempt, achieve )
-- Retrieve bracketed titled external LCCN permalink
-- Precondition:
-- attempt -- string with presumable LCCN
-- achieve -- additional title formatting desires, like "-"
-- Postcondition:
-- Returns link, or plain attempt if bad LCCN
-- 2013-07-14
local lccn = LCCNfactory( attempt );
local r;
if lccn then
r = LCCNformat( lccn, false );
if r then
if achieve then
r = r .. " " .. LCCNformat( lccn, achieve );
else
r = r .. " " .. r;
end
r = "[http://lccn.loc.gov/" .. r .. "]";
end
else
r = attempt;
end
return r;
end -- LCCNforward()
template.hooks.checkDate = function ( about, assign )
local v = assign.value;
if v then
if not v:match( "^20[01][0-9]-1?[0-9]-[123]?[0-9]$" ) then
flop( about, assign, template.msg.formDate );
end
end
end -- .hooks.checkDate()
template.hooks.check_GKD_SWD = function ( about, assign )
local v = assign.value;
if v then
local mode = DNBvalid( v, false );
if mode == 2011 then
if template.params.TYP then
if template.params.TYP.value then
local set;
if about == "GKD" then
set = "^[gkv]$";
elseif about == "SWD" then
set = "^[gksw]$";
end
if template.params.TYP.value:match(set) then
mode = false;
end
end
end
if mode then
fault( about .. template.msg.GKD_SWD );
end
elseif mode then
flop( about, assign, template.msg.DNBpre );
else
flop( about, assign, template.msg.DNBbad );
end
end
end -- .hooks.check_GKD_SWD()
template.hooks.checkGND = function ( about, assign )
local v = assign.value;
if v then
if not DNBvalid( v, false ) then
flop( about, assign, template.msg.DNBbad );
end
end
end -- .hooks.checkGND()
template.hooks.checkGND2012 = function ( about, assign )
local v = assign.value;
if v then
if v:match( "-" ) then
flop( about, assign, template.msg.DNBpre );
else
template.hooks.checkGND( about, assign );
end
end
end -- .hooks.checkGND2012()
template.hooks.checkJa1 = function ( about, assign )
local v = assign.value;
if v then
if v:lower() ~= "ja" and v ~= "1" then
flop( about, assign, template.msg.formYes );
end
end
end -- .hooks.checkJa1()
template.hooks.checkLCCN = function ( about, assign )
local v = assign.value;
if v then
local lccn = LCCNfactory( v );
local sayPre = false;
local saySer = false;
local sayYear = false;
if lccn then
local s = lccn.pre;
if s then
if s:match( "^[ns][bhjopr]?$" ) then
if s == "n" or s == "nb"
or s == "no"
or s == "nr"
or s == "sh"
or s == "sj"
or s == "sp" then
s = false;
end
end
if s then
sayPre = mw.ustring.format( template.msg.LCCNpre,
s );
end
end
s = lccn.year;
if s then
local n = tonumber( s );
if not ( n == 0 or n == 42 or n == 50
or ( n >= 77 and n <= 99 )
or n >= 2001 ) then
sayYear = mw.ustring.format( template.msg.LCCNyear,
s );
end
end
if lccn.serial then
if not lccn.serial:match( "^[0-9]+$" ) then
saySer = mw.ustring.format( template.msg.LCCNser,
lccn.serial );
end
else
saySer = template.msg.LCCNser0;
end
if sayPre or sayYear then
lccn = false;
else
v = mw.ustring.format( "%s/%s/%s",
lccn.pre,
lccn.year,
lccn.serial );
end
end
if not lccn or v ~= assign.value then
flop( about, assign );
template.params[ "LCCN$error" ] =
{ value = template.errorStyle };
lccn = false;
end
if sayPre then
fault( sayPre );
end
if sayYear then
fault( sayYear );
end
if saySer then
fault( saySer );
lccn = false;
end
if lccn then
template.params.LCCNlink = { value = LCCNforward( v ) };
end
end
end -- .hooks.checkLCCN()
template.hooks.checkNumeric = function ( about, assign )
local v = assign.value;
if v then
if not v:match( "^[0-9]+$" ) then
flop( about, assign );
template.params[ about .. "$error" ] =
{ value = template.errorStyle };
end
end
end -- .hooks.checkNumeric()
template.hooks.checkTYP = function ( about, assign )
local v = assign.value;
if v then
if not v:match( "^[pkvwsg]$" ) then
flop( about, assign, template.msg.TYPval );
end
template.params[ "TYP$" ] = { value = v:sub( 1, 1 ):lower() };
else
fault( template.msg.TYPnull );
end
end -- .hooks.checkTYP()
local fun = function( arglist, apply, area )
-- Main function
-- Precondition:
-- arglist -- template args
-- apply -- error style
-- area -- source text of page, or false
-- Return:
-- table with all known template parameters
-- enriched by some created parameters
local e, k, v;
local r = { };
if apply then
template.errorStyle = apply;
end
fiat( arglist );
if area then
k = find( area, "Normdaten" );
if k then
-- Wartungskat
if k == -3 then
-- multiple transclusions
elseif k == -2 then
-- not found -- transclusion in transcluded page
end
end
end
if template.say then
v = template.say:gsub( "\n\n", "<br />" )
:gsub( "\n", " " );
template.params.errors = { value = v };
end
for k, e in pairs( template.params ) do
if e.value then
r[ k ] = e.value;
end
end -- for k, v
return r;
end -- fun()
-- Export
local p = {};
function p.test( args, apply )
local lucky, r = pcall( fun,
args,
apply,
false );
return r;
end
function p.f( frame )
local lucky, r = pcall( fun,
frame:getParent().args,
frame.args.errorStyle,
mw.title.getCurrentTitle():getContent() );
if lucky then
r = frame:expandTemplate{ title = template.sub,
args = r };
end
return r;
end
return p