Modul:Vorlage:Personendaten

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen
local Personendaten = { serial = "2017-01-20" }
--[=[
{{Personendaten}}
]=]



local Config = {
   errCatTop    = "Wikipedia:Vorlagenfehler/",
   errMsg       = { bidi     = "[[Bidirektionales Steuerzeichen]]",
                    casing   = "Groß-/Kleinschreibung",
                    empty    = "Pflichtparameter leer",
                    required = "Pflichtparameter fehlt",
                    unknown  = "Unbekannt",
                    FATAL    = "Interner Fehler" },
   errTypesArgs = { "unknown", "casing", "empty", "required", "bidi" },
   maxAge       = 140,
   order        = { "NAME",
                    "ALTERNATIVNAMEN",
                    "KURZBESCHREIBUNG",
                    "GEBURTSDATUM",
                    "GEBURTSORT",
                    "STERBEDATUM",
                    "STERBEORT",
                    "SORTIERUNG" },
   params       = { NAME             = { lazy=false, least=true },
                    ALTERNATIVNAMEN  = { lazy=true,  least=true },
                    ALTERNATIVNAME   = { },
                    KURZBESCHREIBUNG = { lazy=false, least=true },
                    GEBURTSDATUM     = { lazy=false, least=true },
                    GEBURTSORT       = { lazy=true,  least=true },
                    STERBEDATUM      = { lazy=true,  least=true },
                    STERBEORT        = { lazy=true,  least=true },
                    SORTIERUNG       = { lazy=true,  least=false } },
   section      = "Vorlage_Personendaten",
   self         = "Vorlage:Personendaten",
   title        = mw.title.getCurrentTitle(),
   warnings     = { NAME             =
                       { find = { ["("] = "enthält '('",
                                  ["#"] = "enthält '#'" }
                       },
                    ALTERNATIVNAMEN  =
                       { find  = { ["Pseud."] = "Pseudonym",
                                   ["bürgerlicher Name"]
                                              = "wirklicher Name",
                                   ["eigentlicher Name"]
                                              = "wirklicher Name",
                                   ["richtiger Name"]
                                              = "wirklicher Name",
                                   ["bürgerlich "]
                                              = "wirklicher Name",
                                   ["eigentlich "]
                                              = "wirklicher Name",
                                   ["voller Name"]
                                              = "vollständiger Name" }
                       },
                  --KURZBESCHREIBUNG = { },
                    GEBURTSDATUM     =
                       { find  = { Taufdatum    = "getauft",
                                   Taufe        = "getauft",
                                   bezeugt      = "vor …",
                                   dokumentiert = "vor …",
                                   ["erwähnt"]  = "vor …",
                                   nachweisbar  = "vor …" }
                       },
                    GEBURTSORT       = { link = true },
                    STERBEDATUM      =
                       { find  = { beerdigt        = "begraben",
                                   Beerdigung      = "begraben",
                                   ["Begräbnis"]   = "begraben",
                                   ["frühestens "] = "nach …",
                                   ["nicht vor"]   = "nach …",
                                   vermisst        = "nach …",
                                   ["vermißt"]     = "nach …",
                                   verschollen     = "nach …" }
                       },
                    STERBEORT        = { link = true },
                    Datum            =
                       { find  = { Jh = "Jahrhundert",
                                   ["ungefähr "]   = "um …",
                                   ["gegen "]      = "um …",
                                   ["etwa "]       = "um …",
                                   ["circa "]      = "um …",
                                   ["ca."]         = "um …",
                                   ["~"]           = "um …",
                                   ["≈"]           = "um …",
                                   ["später als"]  = "nach …",
                                   ["frühestens"]  = "nach …",
                                   ["nicht vor"]   = "nach …",
                                   ["oder später"] = "nach …" },
                         match = { ["%.Jahrhundert"]
                                                = "(Punkt Leerzeichen)",
                                   ["v%.%s*[du]%.%s*Z%."] = "v. Chr.",
                                   ["n%.%s*[du]%.%s*Z."]  = "(ohne)",
                                   ["u%.%s*Z."]           = "(ohne)",
                                   ["n%.%s*Chr%."]        = "(ohne)",
                                   ["Ende des"]
                                            = "um (mittlere Jahrezahl)" }
                       }
                  }
}
local Errors, Lookup
if mw.site.server:find( "wmflabs", 1, true ) then
    local lucky, ignore = pcall( require, "No Globals" )
    Lookup = true
end



Personendaten.fetch = function ( assigned, acquire )
    -- Binde Standardbibliotheks-Modul ein
    -- Parameter:
    --     assigned  -- string mit Name
    --                  "DateTime"
    --                  "FormatNum"
    --     acquire   -- string mit abweichendem Modulnamen, oder false
    -- Rückgabewert: table des Moduls
    -- error: Modul nicht gefunden
    local r
    if Personendaten.extern then
        r = Personendaten.extern[ assigned ]
    else
        Personendaten.extern = { }
    end
    if not r then
        local s = assigned
        local lucky, g
        if acquire then
            s = acquire
        end
        lucky, g = pcall( require, "Module:" .. s )
        if type( g ) == "table" then
            r = g[ assigned ]()
            Personendaten.extern[ assigned ] = r
        else
            fehler( "Modul", g )
            error( string.format( "Personendaten.fetch(%s) %s", s, g ) )
        end
    end
    return r
end -- Personendaten.fetch()



local function failed( album, add )
    -- Collect single errors
    --    album  -- string, with collection name
    --    add    -- string, with arg name
    Errors = Errors or { }
    Errors[ album ] = Errors[ album ] or { }
    table.insert( Errors[ album ],  add .. "=" )
end -- failed()



local function failure( alert )
    -- Perform error handling
    --     alert  -- string, with message
    -- Returns appropriate string
    local err, r
    mw.addWarning( string.format( "<br>[[#%s|PERSONENDATEN]] &#8211; %s",
                                  Config.section, alert ) )
    err = mw.html.create( "span" )
                 :addClass( "error" )
                 :addClass( "metadata" )      -- Bis Altbestand sauber
                 :css( "display", "none" )    -- Bis Altbestand sauber
                 :wikitext( string.format( "[[%s]] &#8211; %s",
                                           Config.self, alert ) )
    r   = tostring( err )
    if Config.title.namespace == 0 then
        local s = string.format( "%s/%s",
                                 Config.errCatTop, Config.self )
        r = string.format( "%s[[Category:%s]]", r, s )
    end
    return r
end -- failure()



local function fair( assert, assigned )
    -- Content validation
    --     assert    -- table, with rules for this parameter
    --     assigned  -- string, with parameter value
    -- Returns  string with error message, or not
    local r, rules, s
    local fairy = function ( a )
              if r then
                  r = string.format( "%s, %s", r, a )
              else
                  r = a
              end
          end -- fair.fairy()
    if assigned:match( "&#?%w+;" ) then
        fairy( "HTML-Entities unzulässig" )
    end
    if assigned:find( "{{", 1, true ) then
        fairy( "Vorlagenbenutzung unzulässig" )
    end
    if not assert.link and assigned:find( "[[", 1, true ) then
        fairy( "Wikilink unzulässig" )
    end
    if assert.find then
        for k, v in pairs( assert.find ) do
            if mw.ustring.find( assigned, k, 1, true ) then
                s = string.format( "'%s' statt '%s' verwenden",
                                   v,  mw.text.trim( k ) )
                fairy( s )
            end
        end -- for k, v
    end
    if assert.match then
        for k, v in pairs( assert.match ) do
            s = mw.ustring.match( assigned, k )
            if s then
                s = string.format( "'%s' statt '%s' verwenden", v, s )
                fairy( s )
            end
        end -- for k, v
    end
    return r
end -- fair()



local function features( args )
    -- Parameter validation
    --     args  -- table, with template parameters
    -- Returns  string with error message, or not
    local r, s
    for k, v in pairs( args ) do
        s = k:upper()
        if Config.params[ s ] then
            Config.params[ s ].s = v
            if s ~= k then
                failed( "casing", k )
            end
        else
            failed( "unknown", k )
        end
    end -- for k, v
    if Config.params.SORTIERUNG.s == "-" then
        Config.params.SORTIERUNG.s = ""
    end
    s = Config.params.ALTERNATIVNAME.s
    if s then
        failed( "unknown", "ALTERNATIVNAME" )
        if not Config.params.ALTERNATIVNAMEN.s then
            Config.params.ALTERNATIVNAMEN.s = s
        end
    end
    for k, v in pairs( Config.params ) do
        if v.least then
            if v.s then
                if v.s == "" then
                    v.s = false
                    if not v.lazy then
                        failed( "empty", k )
                    end
                end
            else
                failed( "required", k )
            end
        end
    end -- for k, v
    if Errors then
        local e
        for k, v in pairs( Config.errTypesArgs ) do
            e = Errors[ v ]
            if e then
                r = string.format( "%s: %s",
                                   Config.errMsg[ v ],
                                   table.concat( e, ", " ) )
                break -- for k, v
            end
        end -- for k, v
    end
    return r
end -- features()



local function fence( arg, at )
    -- Day validation
    --     arg  -- string, with parameter name
    --     at   -- string, with single point of time
    -- Returns  string with error message, or not
    local DateTime = Personendaten.fetch( "DateTime" )
    local s = at
    local d, r
    if s:match( "^%l" ) then
        s = s:gsub( "^begraben ", "" )
             :gsub( "^getauft ", "" )
             :gsub( "^nach ", "" )
             :gsub( "^um ", "" )
             :gsub( "^vor ", "" )
    end
    d = DateTime( s )
    if not d then
        r = string.format( "Datum nicht erkannt: '%s'", s )
    elseif d.year then
        if d.month then
            local suggest, suitable
            if d.dom then
                suitable = "j. F Y"
            else
                suitable = "F Y"
            end
            suggest = d:format( suitable )
            if s == suggest then
                if d > DateTime() then
                    r = string.format( "Datum in der Zukunft: '%s'", s )
                else
                    Config.params[ arg ].datetime = d
                end
            else
                r = string.format( "'%s' soll sein '%s'", s, suggest )
            end
        end
    else
        r = string.format( "Jahr fehlt '%s'", s )
    end
    return r
end -- fence()



local function fenced( arg, assigned )
    -- Date expression validation
    --     arg       -- string, with parameter name
    --     assigned  -- string, with parameter value
    -- Returns  string with error message, or not
    local r = fair( Config.warnings.Datum, assigned )
    if not r  and  not assigned:find( ". Jahr", 2, true ) then
        local days
        if assigned:find( "zwischen", 1, true ) then
            days = { false, false }
            days[ 1 ], days[ 2 ] =
                           assigned:match( "^zwischen (%s+) und (%s+)$" )
            if not days[ 1 ] then
                r = "Zeitspanne unverständlich"
            end
        else
            days = mw.text.split( assigned, " oder " )
        end
        if not r  and  not assigned:find( "/", 2, true ) then
            for k, v in pairs( days ) do
                r = fence( arg, v )
                if r then
                    break
                end
            end -- for k, v
        end
    end
    return r
end -- fenced()



local function fences()
    -- Date sequence validation
    local r, r2
            d0 = Config.params.GEBURTSDATUM.datetime
            d  = Config.params.STERBEDATUM.datetime
            if d0 and d then
                if d < d0 then
                    r = "Sterbedatum vor Geburtsdatum"
                else
                    s = string.format( "%d years", Config.maxAge )
                    if d > d0:future( s ) then
                        r = string.format( "Lebensspanne über %d Jahre",
                                           Config.maxAge )
                    end
                end
            end
end -- fences()



local function fill( arg )
    -- Create table row, and check values
    --     arg  -- string, with parameter name
    -- Returns
    --     1. nil, or string with table row, starting with \n
    --     2. nil, or string with error message
    -- Returns string with table row, starting with \n
    local s = Config.params[ arg ].s
    local r, r2
    if s  and  Config.params[ arg ].least then
        local warn = Config.warnings[ arg ]
        r = string.format( "\n|-\n|style='color:#AAAAAA'|%s\n|%s",
                           arg, s )
        if warn then
            r2 = fair( warn, s )
            if not r2  and  arg:find( "DATUM", 5, true ) then
                r2 = fenced( arg, s )
            end
            if r2 then
                r2 = string.format( "Wert von %s: %s", arg, r2 )
            end
        end
    end
    return r, r2
end -- fill()



local function flair( already )
    -- Refine sort string for princes but not artists (no comma in NAME=)
    -- Returns string with sort key
    local r = already
    if  r:match( " [IVXL]" )   and
        ( r:match( " [IVXL]+[ .,]" )  or  r:match( " [IVXL]+$" ) ) then
        local start, s, space, suffix
        start, s, suffix = r:match( "^(.+ )([IVXL]+)%.(.*)$" )
        if not s then
            start, s, suffix = r:match( "^(.+ )([IVXL]+)([ ,].*)$" )
        end
        if s then
            local FormatNum = Personendaten.fetch( "FormatNum" )
            if type( FormatNum.roman2number ) == "function" then
                local k = FormatNum.roman2number( s )
                if k then
                    r = string.format( "%s%d%s", start, k, suffix )
                else
                    failed( "FATAL", "FormatNum.roman2number.type" )
                end
            else
                failed( "FATAL", "FormatNum.roman2number" )
            end
            s = Config.title.text:match( " %((.+)%)$" )
            if s then
                r = string.format( "%s #%s", r, s )
            end
        end
    end
    return r
end -- flair()



local function flat()
    -- Build sort string from NAME=
    -- Returns string with sort key
    local r = Config.params.NAME.s
    local c, k, lrmetc, r2, s
    for i = mw.ustring.len( r ), 1, -1 do
        c = mw.ustring.codepoint( r, i, i )
        if c < 128 then
            if c == 34  or      -- "
               c == 39  or      -- '
               c == 45  or      -- -
               c == 96  then    -- grave
                                -- . (?)
                s = ""
            elseif c == 95 then    -- _
                s = " "
                c = 32
            end
        elseif c >= 160  and  c <= 255 then
            if c == 160 then                      -- nbsp
                s = " "
                c = 32
            elseif c == 173  or  c == 180 then    -- shy, acute
                s = ""
            elseif c == 222 then                  -- THORN
                s = "Th"
            elseif c == 254 then                  -- thorn
                s = "th"
            end
        elseif c <= 0x024F then                   -- Latin (591)
        elseif c <= 0x036F then
            if c >= 697  and  c <= 735 then       -- MODIFIER
                s = ""
            end
        elseif c <= 0x1CF9 then                   -- Non-Latin (7417)
            if c == 0x061C  or                    -- ARABIC LETTER MARK
               c == 0x0639 then                   -- ARABIC LETTER AIN
                s = ""
            end
        elseif c <= 0x1EFF then                   -- Latin (7935)
        elseif c <= 0x2069 then
            if c >= 8192  and  c <= 8202 then
                s = " "
                c = 32
            elseif c >= 8203  and  c <= 8223 then
                s      = ""
                lrmetc = ( lrmetc  or  c == 8206  or  c == 8207 )
            elseif c >= 8234  and  c <= 8238 then
                s      = ""
                lrmetc = true
            elseif c == 8239 then
                s = " "
                c = 32
            elseif c >= 8294  and  c <= 8297 then
                s      = ""
                lrmetc = true
            end
        end
        if c == 32  and  k == 32 then
            s = ""
        end
        if s then
            local start
            if i == 1 then
                start = ""
            else
                start = mw.ustring.sub( r,  1,  i - 1 )
            end
            r = string.format( "%s%s%s",
                               start,
                               s,
                               mw.ustring.sub( r,  i + 1 ) )
            s = false
        end
        k = c
    end -- for i
    if r:find( ",", 1, true ) then
        if Config.params.NAME.s:find( ",[^ ]" ) then
            r  = r:gsub( ",([^ ])", ", $1" )
            r2 = "Leerzeichen nach Komma erforderlich"
        end
    else
        r = flair( r )
    end
    if lrmetc then
        failed( "bidi", "NAME" )
    end
    return r, r2
end -- flat()



local function furnish()
    -- Create table and defaultsort
    -- Returns
    --     1. appropriate string
    --     2. nil, or string with error message
    local r = string.format( "{| id='%s' class='%s' style='%s'",
                             Config.section,
                             "metadata rahmenfarbe1",
                             "border-style: solid; margin-top: 20px;" )
    local r2, s, s2
    for i = 1, 8 do
        s, s2 = fill( Config.order[ i ] )
        if s then
            r = r .. s
            if s2 then
                if r2 then
                    r2 = string.format( "%s; %s", r2, s2 )
                else
                    r2 = s2
                end
            end
        end
    end -- for i
    fences()
    r = r .. "\n|}"
    s = Config.params.SORTIERUNG.s
    if s ~= "" then
        if not s and Config.params.NAME.s then
            s, s2 = flat()
            if s2 then
                if r2 then
                    r2 = string.format( "%s; %s", r2, s2 )
                else
                    r2 = s2
                end
            end
        end
        if s then
            s = string.format( "{{SORTIERUNG:%s}}", s )
            if Config.frame then
                r = r .. Config.frame:preprocess( s )
                if Lookup then
                    r = string.format( "%s\n<code>%s</code>", r, s )
                end
            else
                r = string.format( "%s\n%s", r, s )
            end
        end
    end
    return r, r2
end -- furnish()



-- Export
local p = { }

p.main = function ( args )
    -- Invocation
    --     args   -- table, with template parameters
    -- Returns appropriate string
    local screamArgs = features( args )
    local r, screamVals = furnish()
    if screamArgs or screamVals then
        local s
        if screamArgs and screamVals then
            s = string.format( "%s; %s", screamArgs, screamVals )
        elseif screamArgs then
            s = screamArgs
        else
            s = screamVals
        end
        r = string.format( "%s\n%s", r, failure( s ) )
    end
    return r
end -- p.main()



p.f = function ( frame )
    local lucky, r
    Config.frame = frame
    lucky, r = pcall( p.main, frame:getParent().args )
    if not lucky then
        r = failure( r )
    end
    return r
end -- p.f()



function p.failsafe()
    return Personendaten.serial
end



p.Personendaten = function ()
    return Personendaten
end -- p.Personendaten

return p