Modul:Vorlage:Normdaten

Aus Beta Wikipedia
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