Modul:Vorlage:Anker

aus Wikipedia, der freien Enzyklopädie
local Anchors = { suite   = "Anchors",
                  serial  = "2020-04-10",
                  item    = 79414611,
                  globals = { Multilingual = 47541920,
                              TemplUtl     = 52364930 }
                }
--[=[
Support Template:Anchor   {{Anker}}
]=]
local Failsafe  = Anchors
local GlobalMod = Anchors



local Config = {
    badPattern     = "[#'\"%[%]<>|]",
    globalPage     = { "antispam-container",
                       "bodyContent",
                       "catlinks",
                       "centralNotice",
                       "content",
                       "contentSub",
                       "contentSub2",
                       "editform",
                       "editpage-copywarn",
                       "firstHeading",
                       "footer",
                       "fundraising",
                       "jump-to-nav",
                       "language-settings-dialog",
                       "languagesettings-panels",
                       "page-actions",
                       "siteNotice",
                       "siteSub",
                       "toc",
                       "toctitle",
                       "top",
                       "wikiPreview" },
    globalPatterns = { "^mw%-[%l%-]+$",
                       "^ca%-[%l%-]+$",
                       "^n%-[%l%-]+$",
                       "^p%-%a%a[%a%-]+$",
                       "^pt%-[%l%-]+$",
                       "^wp%u" },
    errCat         = false,
    errClass       = "error_fragment",
    errHide        = true,
    errNS          = false,
    errInvalid     = { en = "Invalid:",
                       de = "Ungültig:" },
    errModule      = { en = "Library module missing:",
                       de = "Bibliotheksmodul fehlt:" },
    errNoFragments = { en = "No fragments given",
                       de = "Keine Bezeichner angegeben" },
    errUnkown      = { en = "Unkown parameter:",
                       de = "Parameter unbekannt:" }
}



local foreignModule = function ( access, advanced, append, alt, alert )
    -- Fetch global module
    -- Precondition:
    --     access    -- string, with name of base module
    --     advanced  -- true, for require(); else mw.loadData()
    --     append    -- string, with subpage part, if any; or false
    --     alt       -- number, of wikidata item of root; or false
    --     alert     -- true, for throwing error on data problem
    -- Postcondition:
    --     Returns whatever, probably table
    -- 2020-01-01
    local storage = access
    local finer = function ()
                      if append then
                          storage = string.format( "%s/%s",
                                                   storage,
                                                   append )
                      end
                  end
    local fun, lucky, r, suited
    if advanced then
        fun = require
    else
        fun = mw.loadData
    end
    GlobalMod.globalModules = GlobalMod.globalModules or { }
    suited = GlobalMod.globalModules[ access ]
    if not suited then
        finer()
        lucky, r = pcall( fun,  "Module:" .. storage )
    end
    if not lucky then
        if not suited  and
           type( alt ) == "number"  and
           alt > 0 then
            suited = string.format( "Q%d", alt )
            suited = mw.wikibase.getSitelink( suited )
            GlobalMod.globalModules[ access ] = suited or true
        end
        if type( suited ) == "string" then
            storage = suited
            finer()
            lucky, r = pcall( fun, storage )
        end
        if not lucky and alert then
            error( "Missing or invalid page: " .. storage )
        end
    end
    return r
end -- foreignModule()



local function factory( apply )
    -- Localization of messages
    --     apply  -- string, with message key
    -- Returns message text; at least English
    -- TODO: Might be extended by tabData and Multilingual
    local entry = Config[ apply ]
    local r
    if entry then
        -- TODO: page language
        r = entry[ mw.language.getContentLanguage():getCode() ]
        if not r then
            r = entry.en
        end
    else
        r = tostring( mw.html.create( "span" )
                        :addClass( "error" )
                        :wikitext( string.format( "????.%s.????",
                                                  apply ) ) )
    end
    return r
end -- factory()



local function faculty( adjust )
    -- Test template arg for boolean
    --     adjust  -- string or nil
    -- Returns boolean
    local r
    if type( adjust ) == "string" then
        r = mw.text.trim( adjust )
        if r ~= ""  and  r ~= "0" then
            r = true
        end
    else
        r = adjust or false
    end
    return r
end -- faculty()



local function fair( assembly )
    -- Create HTML code for anchors
    --     assembly  -- sequence table, with identifiers
    -- Returns HTML string
    local collection = mw.html.create( "" )
    local f = function ( a )
                  collection:node( mw.html.create( "span" )
                                          :attr( "id", a ) )
              end
    local s, seen, shift
    table.sort( assembly )
    for i = 1, #assembly do
        s = assembly[ i ]
        if s ~= seen then
            if not s:match( "^[%w_]+$" ) then
                shift = mw.uri.encode( s, "WIKI" )
                if shift ~= s then
                    f( shift:gsub( "%%", "." ) )
                end
            end
            f( s )
            seen = s
        end
    end -- i = 1, #assembly
    return tostring( collection )
end -- fair()



local function fault( alert, about )
    -- Format message with class="error" or similar
    --     alert  -- string, with message key
    --     about  -- string, with explanation
    -- Returns message with markup
    local scope = Config.errClass
    local story = factory( alert )
    local TemplUtl = foreignModule( "TemplUtl",
                                    true,
                                    false,
                                    Anchors.globals.TemplUtl )
    local r, scope, style
    if type( TemplUtl ) == "table"  and
       type( TemplUtl.TemplUtl ) == "function" then
        TemplUtl = TemplUtl.TemplUtl()
    else
        TemplUtl = false
    end
    if Config.self then
        story = string.format( "%s * %s", Config.self, story )
    end
    if Config.errClasses then
        if scope then
            scope = string.format( "%s %s",
                                   scope, Config.errClasses )
        else
            scope = Config.errClasses
        end
    end
    if about then
        story = string.format( "%s %s", story, about )
    end
    if TemplUtl then
        r = TemplUtl.failure( story,
                              not Config.errHide,
                              scope,
                              Config.frame )
    else
        r = tostring( mw.html.create( "span" )
                             :addClass( scope )
                             :addClass( "error" )
                             :wikitext( story ) )
    end
    if Config.errCat then
        if Config.errNS then
            local st = type( Config.errNS )
            if st == "number" then
                st = { }
                table.insert( st, Config.errNS )
                Config.errNS = st
            elseif st == "string" then
                Config.errNS = mw.text.split( Config.errNS, "%s+" )
                for i = 1, #Config.errNS do
                    Config.errNS[ i ] = tonumber( Config.errNS[ i ] )
                end -- for i
            end
            if type( Config.errNS ) == "table" then
                local ns = mw.title.getCurrentTitle().namespace
                for i = 1, #Config.errNS do
                    if Config.errNS[ i ] == ns then
                        Config.errNS = false
                        break    -- for i
                    end
                end -- for i
            end
        end
        if not Config.errNS then
            r = string.format( "%s[[Category:%s]]", r, Config.errCat )
        end
    end
    return r
end -- fault()



local function features( adapt )
    -- Set parameters on compliance
    if type( adapt ) == "table" then
        Config.limitMarkup = faculty( adapt.rejectMarkup )
        Config.limitMW     = faculty( adapt.rejectMW )
        Config.limitXML    = faculty( adapt.onlyXML )
        Config.lightXML    = faculty( adapt.softXML )
    end
end -- features()



local function main( argsF, argsT )
    -- Invocation
    --     argsF  -- table, with #invoke parameters, or false
    --     argsT  -- table, with template parameters
    -- Returns appropriate string
    local r
    if type( argsF ) == "table" then
        Config.errCat     = argsF.errCat
        Config.errClasses = argsF.errClasses
        Config.errHide    = faculty( argsF.errHide )
        Config.errNS      = argsF.errNS
        features( argsF )
if mw.site.server:find( "de.wikipedia.", 1, true ) then
    Config.errClasses  = "Linkwartung"
    Config.errHide     = true
    Config.errNS       = 0
    Config.limitMarkup = true
    Config.limitMW     = false
    Config.limitXML    = true
    Config.lightXML    = true
end
    end
    if type( argsT ) == "table" then
        local e, got, less, s, unknown
        for k, v in pairs( argsT ) do
            if type( k ) == "number" then
                v = mw.text.trim( v )
                if v == "" then
                    v = false
                end
                less = ( k < 0 )
                k = false
            else
                less = k:match( "^[x%-][1-9]%d*" )
                if less then
                    k = false
                end
            end
            if k then
                unknown = unknown  or  { }
                table.insert( unknown, k )
            elseif v then
                got      = got  or  { }
                v        = v:gsub( " +", "_" )
                            :gsub( "_+", "_" )
                got[ v ] = less
            end
        end -- for k, v
        if unknown then
            e = mw.html.create( "code" )
                  :css( "white-space", "nowrap" )
                  :wikitext( table.concat( unknown, " " ) )
            s = string.format( " in [[%s]]",
                               Config.frame:getTitle() )
            e:tag( "span" )
             :wikitext( s )
            r = fault( "errUnkown", tostring( e ) )
        elseif got then
            local profile = { limitMarkup = false,
                              limitMW     = false,
                              limitXML    = false }
            local bad, checked, legal, o, s
            for k, v in pairs( got ) do
                if v then
                    o = profile
                else
                    o = false
                end
                s, legal = Anchors.feasible( k, o )
                if s then
                    checked = checked  or  { }
                    table.insert( checked, s )
                end
                if not legal then
                    bad = bad  or  { }
                    e   = mw.html.create( "code" )
                                 :css( "white-space", "nowrap" )
                                 :wikitext( mw.text.encode( k ) )
                    table.insert( bad, tostring( e ) )
                end
            end -- for k, v
            if checked then
                r = fair( checked )
            end
            if bad then
                s = string.format( "%s in [[%s]]",
                                   table.concat( bad, ", " ),
                                   Config.frame:getParent():getTitle() )
                r = string.format( "%s%s",
                                   r or "",
                                   fault( "errInvalid", s ) )
            end
        else
            r = fault( "errNoFragments" )
        end
    end
    return r or ""
end -- main()



Anchors.feasible = function ( analyse, allow )
    -- Check ID for compliance
    -- Precondition:
    --     analyse   -- string, with presumptive anchor
    --     allow     -- optional table, with compliance criteria
    --                  -- limitMarkup
    --                  -- limitMW
    --                  -- limitXML
    --                  -- lightXML
    -- Postcondition:
    --     Returns
    --         1.   -- string  -- with normalised anchor
    --              -- false   -- if not appropriate
    --         2.   -- true, if fully compliant
    local r1 = false
    local r2 = false
    if type( analyse ) == "string" then
        local s = mw.text.trim( analyse )
        if s ~= "" then
            local deny = { limitMarkup = Config.limitMarkup,
                           limitMW     = Config.limitMW,
                           limitXML    = Config.limitXML,
                           lightXML    = Config.lightXML }
            if type( allow ) == "table" then
                for k, v in pairs( deny ) do
                    if type( allow[ k ] ) == "boolean" then
                        deny[ k ] = allow[ k ]
                    end
                end -- for k, v
            end
            if deny.limitMW then
                r1 = true
            else
                s  = mw.text.unstripNoWiki( s )
                s  = mw.text.trim( s )
                r1 = ( s ~= "" )
            end
            if r1   and   deny.limitMarkup   and
               ( s:match( Config.badPattern )  or
                 s:match( "&#%w+;" ) ) then
                r1 = false
            end
            if r1 then
                for k = 1, #Config.globalPage do
                    if s == Config.globalPage[ k ] then
                        r1 = false
                        break    -- k = 1, #Config.globalPage
                    end
                end -- k = 1, #Config.globalPage
                if r1 then
                    Config.badCtrl = Config.badCtrl  or
                                     string.format( "[%c-%c]", 1, 31 )
                    r1 = not s:find( Config.badCtrl )
                    if r1  and  s:match( "%%%x%x" ) then
                        r1 = false
                    end
                    if r1 then
                        for k = 1, #Config.globalPatterns do
                            if s:match( Config.globalPatterns[ k ] ) then
                                r1 = false
                                break    -- k = 1, #Config.globalPatterns
                            end
                        end -- k = 1, #Config.globalPatterns
                    end
                end
            end
            if r1 then
                r2 = true
                if deny.limitXML   and
                   ( s:match( "^%d" )  or
                     s:sub( 1, 1 ) == "-" ) then
                    r2 = false
                    if not deny.lightXML then
                        r1 = false
                    end
                end
            end
            if r1 then
                r1 = s:gsub( " +", "_" )
                      :gsub( "_+", "_" )
            end
        end
    end
    return r1, r2
end -- Anchors.feasible()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version or "wikidata" or "~"
    --                 or false
    -- Postcondition:
    --     Returns  string  -- with queried version, also if problem
    --              false   -- if appropriate
    -- 2019-10-15
    local last  = ( atleast == "~" )
    local since = atleast
    local r
    if last  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local seek = Failsafe.serialProperty or "P348"
                local vsn  = entity:formatPropertyValues( seek )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string"  and
                   vsn.value ~= "" then
                    if last  and  vsn.value == Failsafe.serial then
                        r = false
                    else
                        r = vsn.value
                    end
                end
            end
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Export
local p = { }

p.f = function ( frame )
    local lucky, r
    Config.frame = frame
    lucky, r = pcall( main, frame.args, frame:getParent().args )
    if not lucky then
        r = tostring( mw.html.create( "span" )
                        :addClass( "error" )
                        :wikitext( string.format( "%s * %s",
                                                  frame:getTitle(),
                                                  r ) ) )
    end
    return r
end -- p.f()

p.feasible = function ( frame )
    local r1, r2
    Config.frame = frame
    features( frame.args )
    r1, r2 = Anchors.feasible( frame.args[ 1 ] )
    return r1 or ""
end -- p.feasible()

p.forbidden = function ()
    local r   = ""
    local sep = ""
    local e
    for k = 1, #Config.globalPage do
        e   = mw.html.create( "code" )
                     :css( "white-space", "nowrap" )
                     :wikitext( Config.globalPage[ k ] )
        r   = string.format( "%s%s%s", r, sep, tostring( e ) )
        sep = ", "
    end -- k = 1, #Config.globalPage
    return r
end -- p.forbidden()

p.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return Failsafe.failsafe( since )  or  ""
end -- p.failsafe()

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

return p